#  raw_socket.rb
#
#  This file is part of myvnet.
#
#  Copyright (C) 2008  Filipe David Borba Manana,  fdmanana@gmail.com
#
#  myvnet is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  myvnet is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with myvnet.  If not, see <http://www.gnu.org/licenses/>.
 

require 'socket'
require 'lib/utils'
require 'lib/exceptions'

module Packet

  # Linux Raw Socket. For injection of crafted packets and
  # packet capture.
  #
  class RawSocket
    
    PF_PACKET              = 17        # linux/socket.h
    AF_PACKET              = PF_PACKET # linux/socket.h
    SOL_PACKET             = 263       # linux/socket.h
    PACKET_ADD_MEMBERSHIP  = 1         # linux/if_packet.h
    PACKET_MR_PROMISC      = 1         # linux/if_packet.h
    ETH_P_ALL              = 0x00_03   # linux/if_ether.h
    SIOCGIFINDEX           = 0x89_33   # bits/ioctls.h
    
    
    attr_reader :interface
    
    
    # === Synopsis
    # 
    #   Packet::RawSocket#initialize(interface, promisc_mode = false)
    # 
    # === Args
    # 
    # +interface+::
    #   Name of the network interface to bind the Raw Socket to.
    #   
    # +promisc_mode+::
    #   If true turns on promiscuous mode.
    # 
    # === Exceptions
    # 
    # +RawSocketError+::
    #   If some error occurred while creating the Raw Socket.
    # 
    # === Description
    # 
    # Instantiates a Linux Raw Socket.
    #
    def initialize(interface, promisc_mode = false)
      @interface = interface
      
      @sock = nil
      begin
        # check Linux man page 7 for "packet"
        @sock = Socket.new(PF_PACKET, Socket::SOCK_RAW,
          Utils.htons(ETH_P_ALL))
        
        # struct ifreq in net/if.h        
        ifreq = [@interface].pack 'a16'
        ifreq << ( [""].pack 'a16' )
        @sock.ioctl(SIOCGIFINDEX, ifreq)
        
        if_index = ifreq[16 .. (16 + Utils::SIZEOF_INT)]
        
        # struct sockaddr_ll in linux/if_packet.h
        sll = [AF_PACKET].pack 'S'                    # sll_family
        sll << ( [Utils.htons(ETH_P_ALL) ].pack 'S' ) # sll_protocol
        sll << if_index                               # sll_ifindex
        sll << ( [0].pack 'S' )                       # sll_hatype          
        sll << ( [0].pack 'C' )                       # sll_pkttype
        sll << ( [0].pack 'C' )                       # sll_halen
        sll << ( ([0].pack 'C') * 8 )                 # sll_addr[8]
        @sock.bind sll
        
        if promisc_mode
          # struct packet_mreq in linux/if_packet.h
          mreq = if_index
          mreq << ( [PACKET_ADD_MEMBERSHIP].pack 'S' )
          mreq << ( [0].pack 'C' )
          mreq << ( ([0].pack 'C') * 8 )

          @sock.setsockopt(SOL_PACKET, PACKET_ADD_MEMBERSHIP, mreq)
        end
        
      rescue Exception => ex
        @sock.close if @sock        
        raise RawSocketError, ex.message
      end
    end
    
    
    # === Synopsis
    # 
    #   Packet::RawSocket#send(packet_bytes)
    # 
    # === Args
    # 
    # +packet_bytes+::
    #   The packet to inject into the network, as a byte stream.
    # 
    # === Return
    # 
    # The number of bytes sent through the socket.
    #    
    # === Description
    # 
    # Injects a packet into the network.
    #    
    def send(packet_bytes)     
      sent = -1
      
      begin
        sent = @sock.write packet_bytes
        @sock.flush
      rescue Exception => ex
        $log[:packet].error "RawSocket send exception: #{ex.message}\n"
      end
      
      sent
    end
    

    # === Synopsis
    # 
    #   Packet::RawSocket#recv
    # 
    # === Return
    # 
    # A byte stream representing a packet captured from the network.
    #    
    # === Exception
    # 
    # +Interrupt+::
    #   If the socket read operation was interrupted.
    # 
    # +RawSocketError+::
    #   If an error occured while reading from the socket.
    # 
    # === Description
    # 
    # Captures a packet from the network.
    #        
    def recv
      data = nil
      
      begin
        data = (@sock.recvfrom 2048)[0]
      rescue Interrupt
        raise
      rescue Exception => ex
        raise RawSocketError, ex.message
      end
      
      data
    end
      
      
    def close
      @sock.close
    end
    
  end

end